文章目錄
  1. 1. Binary Indexed Tree 树状数组
    1. 1.1. 引入的问题
    2. 1.2. 问题1
    3. 1.3. 问题2
    4. 1.4. 树状数组的解法
      1. 1.4.1. 基本设计
      2. 1.4.2. 求解问题2
      3. 1.4.3. 求解问题1
      4. 1.4.4. 求解MeetingRoomII

Binary Indexed Tree 树状数组

做Leetcode 做到MeetingRoomII的时候我知道不用线段树或者树状数组是不太好搞了。还是来学习一下吧。

树状数组算是线段树的一种特殊情况(子集),所以树状数组能解决的问题线段树一定能做,但线段树能做的树状数组不一定能做。

引入的问题

问题1

对一个数组进行如下操作
update(i1,i2,operation)value(i)=?

例如:int[] ar=new int[N]
update(ar,2,5,+,4) //(2 to 5).map(ar(_)+=4)
return ar(k)

问题2

对一个数组进行如下操作 updateindex,operation,value)
求 sum(i1,i2)=?


例如:int[] ar=new int[N]
update(ar,3,+,5);     //ar[3]+=5
update(ar,11,-,3);  //ar[11]-=3
。。。。。。
求:sum(ar,0,12)? //(0 to 12).map(ar(_)).sum

应用线段树的思路,对问题1,可以在中间节点缓存操作O(lgn),求解时再计算出结果。

对问题2,可以直接缓存sum,每次操作时都对sum附带进行操作O(lgn),而求结果时,直接使用缓存的sum结果O(lgn)。

树状数组的解法

按照刚才的解题分析,应当使用线段树进行操作,但线段树操作效率比较低,是否一种折中的办法呢?

例如:把线段树的每个节点映射到一个额外的数组上?

那么问题很明确,如何将一颗有N个叶子节点的树映射到一个长度为N的数组上?

最直观的思路显然是这样:

图1

图看上去很容易理解,我们希望将中间结果(1~2)(5~8)等存在另外一个数组B[]里,剩下的问题只有一个,怎么把这些节点向数组的index映射?而且这个映射显然是算法可描述的,这样在计算时才能容易找到各个节点。

图2

究竟怎样想出来的这种映射方式已经不得而知,但的确是个很神妙的构想,这也是Binary Index Tree的精髓了。

基本设计

图2中黄色的块被废弃了,如果用a->b表示a存入b则:

(1~2)->2
(1~4)->4
(5~6)->6
(1~8)->8

1->1
3->3
5->5
7->7

是不是有点规律了?

  1. 奇数点全部存原数组值
  2. 偶书点K存入的位数与K&(-K)后面的0相关,由M个0就存了1<<M个数字

求解问题2

如果希望计算(st,ed)的sum时,如何计算呢?直接计算st到ed之间的数据相当难算,但是

1
sumsted)=sum(1,ed)-sum(1,st-1)

这时候再看一下图2是不是明白了?
从新定义一个函数sumFromStart(k)表示从1加到K的和。

1
sum(st,ed)=sumFromStart(ed)-sumFromStart(st-1)

最后看看这个sumFromStart写法吧其实很容易想到:

  • 2 是10计算1~2的和
  • 4 是100计算了1~4的和
  • 8 是1000计算了1~8的和

如果我们想算1~7

7=111=100+10+1

而且1~7=(1~4)+(5~6)+7

所以sumFromStart(7)=B(4)+B(6)+B(7)

再写清楚点,如果是二进制:

sumFromStart(111)=B(100)+B(110)+B(111)

想明白了? 还没有?那就看图吧

图3

所以,sumFromStart(k)定义如下

1
2
3
4
5
6
7
int sumFromStart(int k,int[] b){
int sum=0;
while(k!=0){
sum+=b[k];
k=k-(k&(-k));
}
}

最后还有一个更新操作,因为是单个更新,所以注意要把上面的点也更新了,以对A[5],操作为例,需要更新B(5),B(6),B(8)。写出来这三个

B(101)
B(110)
B(1000)

看不出啥太明显的规律啊?

1
2
101 +1 =110
110 +10=1000

明白了么? k+k&(-k)啊,所以update写出来

1
2
3
4
5
6
7
8
9
10
void update(int index,int v,Operation=add,int[] b){
while(index<b.length){
b[index]+=v;
index=index+index&(~index);
}
}
//问题2返回
void sum(int st,int ed,int[]b){
return sumFromStart(ed,b)-sumFromStart(st-1,b);
}

求解问题1

问题1是段累加,单点求值,所以可以把段累加过程加入B数组(复杂度lgn),求解时再算单点(复杂度lgn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int[] b=new int[N];
void update(int st,int ed,int added,){
updateFromStart(st-1,-added,b);
updateFromStart(ed,added,b);
}
void updateFromStart(int index,int added,int[] b){
while(index<b.length){
b[index]+=added;
index-=index&(-index);
}
}

int getV(int index){
int sum=0;
while(index<b.length){
sum+=b[index];
index+=index&(-index);
}
return sum;
}

求解MeetingRoomII

最后给出meeting RoomII代码,很遗憾,这个题最后求最大重叠,所以遍历了每个点找最大,时间复杂度O(nlgn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public int minMeetingRooms(Interval[] ins) {
int st=Integer.MAX_VALUE;
int ed=Integer.MIN_VALUE;
for(Interval in: ins){
st=Math.min(st,in.start);
ed=Math.max(ed,in.end);
}
int[] bis=new int[ed-st+5];
int delta=st-1;
for(Interval in:ins){
add(in.start-delta,-1,bis);
add(in.end-delta,1,bis);
}
int max=0;
for(int i=1;i<bis.length;i++){
max=Math.max(max,getFrom(bis,i));
}
return max;
}

private int getFrom(int[] bis, int i) {
int res=0;
while(i<bis.length){
res+=bis[i];
i+=i&(-i);
}
return res;
}

private void add(int index,int added,int[] b){
while(index>0){
b[index]+=added;
index-=index&(-index);
}
}
文章目錄
  1. 1. Binary Indexed Tree 树状数组
    1. 1.1. 引入的问题
    2. 1.2. 问题1
    3. 1.3. 问题2
    4. 1.4. 树状数组的解法
      1. 1.4.1. 基本设计
      2. 1.4.2. 求解问题2
      3. 1.4.3. 求解问题1
      4. 1.4.4. 求解MeetingRoomII